using System;
using System.Text;
using System.Threading;

namespace ICSharpCode.TextEditor.Document
{
	public class GapTextBufferStrategy : ITextBufferStrategy
	{
		private int creatorThread = Thread.CurrentThread.ManagedThreadId;

		private char[] buffer = new char[0];

		private string cachedContent;

		private int gapBeginOffset = 0;

		private int gapEndOffset = 0;

		private int gapLength = 0;

		private const int minGapLength = 128;

		private const int maxGapLength = 2048;

		public int Length => this.buffer.Length - this.gapLength;

		private void CheckThread()
		{
			if (Thread.CurrentThread.ManagedThreadId != this.creatorThread)
			{
				throw new InvalidOperationException("GapTextBufferStategy is not thread-safe!");
			}
		}

		public void SetContent(string text)
		{
			if (text == null)
			{
				text = string.Empty;
			}
			this.cachedContent = text;
			this.buffer = text.ToCharArray();
			this.gapBeginOffset = (this.gapEndOffset = (this.gapLength = 0));
		}

		public char GetCharAt(int offset)
		{
			this.CheckThread();
			if (offset < 0 || offset >= this.Length)
			{
				throw new ArgumentOutOfRangeException("offset", offset, "0 <= offset < " + this.Length);
			}
			return (offset < this.gapBeginOffset) ? this.buffer[offset] : this.buffer[offset + this.gapLength];
		}

		public string GetText(int offset, int length)
		{
			this.CheckThread();
			if (offset < 0 || offset > this.Length)
			{
				throw new ArgumentOutOfRangeException("offset", offset, "0 <= offset <= " + this.Length);
			}
			if (length < 0 || offset + length > this.Length)
			{
				throw new ArgumentOutOfRangeException("length", length, "0 <= length, offset(" + offset + ")+length <= " + this.Length);
			}
			if (offset == 0 && length == this.Length)
			{
				if (this.cachedContent != null)
				{
					return this.cachedContent;
				}
				return this.cachedContent = this.GetTextInternal(offset, length);
			}
			return this.GetTextInternal(offset, length);
		}

		private string GetTextInternal(int offset, int length)
		{
			int num = offset + length;
			if (num < this.gapBeginOffset)
			{
				return new string(this.buffer, offset, length);
			}
			if (offset > this.gapBeginOffset)
			{
				return new string(this.buffer, offset + this.gapLength, length);
			}
			int num2 = this.gapBeginOffset - offset;
			int num3 = num - this.gapBeginOffset;
			StringBuilder stringBuilder = new StringBuilder(num2 + num3);
			stringBuilder.Append(this.buffer, offset, num2);
			stringBuilder.Append(this.buffer, this.gapEndOffset, num3);
			return stringBuilder.ToString();
		}

		public void Insert(int offset, string text)
		{
			this.Replace(offset, 0, text);
		}

		public void Remove(int offset, int length)
		{
			this.Replace(offset, length, string.Empty);
		}

		public void Replace(int offset, int length, string text)
		{
			if (text == null)
			{
				text = string.Empty;
			}
			this.CheckThread();
			if (offset < 0 || offset > this.Length)
			{
				throw new ArgumentOutOfRangeException("offset", offset, "0 <= offset <= " + this.Length);
			}
			if (length < 0 || offset + length > this.Length)
			{
				throw new ArgumentOutOfRangeException("length", length, "0 <= length, offset+length <= " + this.Length);
			}
			this.cachedContent = null;
			this.PlaceGap(offset, text.Length - length);
			this.gapEndOffset += length;
			text.CopyTo(0, this.buffer, this.gapBeginOffset, text.Length);
			this.gapBeginOffset += text.Length;
			this.gapLength = this.gapEndOffset - this.gapBeginOffset;
			if (this.gapLength > 2048)
			{
				this.MakeNewBuffer(this.gapBeginOffset, 128);
			}
		}

		private void PlaceGap(int newGapOffset, int minRequiredGapLength)
		{
			if (this.gapLength < minRequiredGapLength)
			{
				this.MakeNewBuffer(newGapOffset, minRequiredGapLength);
				return;
			}
			while (newGapOffset < this.gapBeginOffset)
			{
				this.buffer[--this.gapEndOffset] = this.buffer[--this.gapBeginOffset];
			}
			while (newGapOffset > this.gapBeginOffset)
			{
				this.buffer[this.gapBeginOffset++] = this.buffer[this.gapEndOffset++];
			}
		}

		private void MakeNewBuffer(int newGapOffset, int newGapLength)
		{
			if (newGapLength < 128)
			{
				newGapLength = 128;
			}
			char[] array = new char[this.Length + newGapLength];
			if (newGapOffset < this.gapBeginOffset)
			{
				Array.Copy(this.buffer, 0, array, 0, newGapOffset);
				Array.Copy(this.buffer, newGapOffset, array, newGapOffset + newGapLength, this.gapBeginOffset - newGapOffset);
				Array.Copy(this.buffer, this.gapEndOffset, array, array.Length - (this.buffer.Length - this.gapEndOffset), this.buffer.Length - this.gapEndOffset);
			}
			else
			{
				Array.Copy(this.buffer, 0, array, 0, this.gapBeginOffset);
				Array.Copy(this.buffer, this.gapEndOffset, array, this.gapBeginOffset, newGapOffset - this.gapBeginOffset);
				int num = array.Length - (newGapOffset + newGapLength);
				Array.Copy(this.buffer, this.buffer.Length - num, array, newGapOffset + newGapLength, num);
			}
			this.gapBeginOffset = newGapOffset;
			this.gapEndOffset = newGapOffset + newGapLength;
			this.gapLength = newGapLength;
			this.buffer = array;
		}
	}
}
